var dimensions = [ "Human Phenotype", "Lowest Common Subsumer", "Mammalian Phenotype" ];

//IMPORTANT: the comparison_data variable represents the full dataset as it was initially loaded
var comparison_data = []; 
//IMPORTANT: the filtered_data is the data currently shown on the screen.  It is a subset of the comparison_data
var filtered_data = [], axis_pos_list = [], axis_item_list = [], y_scale_list=[];

//this is temporary
var model_data = [], model_list = ["Model 1", "Model 2", "Model 3", "Model 4", "Model 5",
                                   "Model 6", "Model 7", "Model 8", "Model 9", "Model 10"];

var col_starting_pos = 50, text_width = 150, clicked_data = undefined, xScale = undefined;

//d3.rgb("#44a293") : d3.rgb("#a4d6d4")

var color_scale = d3.scale.linear().domain([0,1]).range([d3.rgb("#e5e5e5"), d3.rgb("#44a293")]);

var lcs_slider = $("#lcs_slider");

var m = [ 30, 10, 10, 10 ], w = 1000 - m[1] - m[3], h = 1500 - m[0] - m[2];
var x = d3.scale.ordinal().rangePoints([ 0, w ], 1);
var line = d3.svg.line(), axis = d3.svg.axis().orient("left"), background, foreground;
var svg = d3.select("body").append("svg").attr("width", w + m[1] + m[3]).attr(
		"height", h + m[0] + m[2]).append("g").attr("transform",
		"translate(" + m[3] + "," + m[0] + ")");

function loadData() {
	//NOTE: This is currently hard-coded to a JSON dataset.  Obviously, this will reference a live dataset in the future
	jQuery.ajax({ url: "data/testshhgenotypemps-compare-atts.json",
		async:false,
		dataType: 'json', 
		success: function(data){
		// build the comparison_data variable
		//note: the attributes added in this method become the basis for the "columns"
	    //shown on the screen in the x.domain(dimensions = d3.keys... code
		//copy the LCS score into all the relationships
		data.forEach(function(data_item, i) {
			//build an object for each item returned in a row of data (column A, subsumer, column B)
			//these items will become the rectangles on the screen
			var colA_label = getCleanLabel(data_item.A.id,data_item.A.label);
			var itemA = {label: getCleanLabel(data_item.A.id,data_item.A.label), score: data_item.LCS_Score, id: data_item.A.id, 
					organism: getOrganism(data_item.A.id), axis: 0, rowid: i};

			var sub_label = getCleanLabel(data_item.LCS.id,data_item.LCS.label);
			var subsumer = {label: getCleanLabel(data_item.LCS.id,data_item.LCS.label), id: data_item.LCS.id, score: data_item.LCS_Score, 
			organism: getOrganism(data_item.LCS.id), axis: 1, rowid: i};
			
			var colB_label = getCleanLabel(data_item.B.id,data_item.B.label);
			var itemB = {label: getCleanLabel(data_item.B.id,data_item.B.label), score: data_item.LCS_Score, id: data_item.B.id, 
					organism: getOrganism(data_item.B.id), axis: 2, rowid: i};

			//add the connections between each object
			//these connections form the lines on the screen
			itemA.connection = subsumer;
			subsumer.connection = itemB;
			
			//add all the relations to the other objects
			//the relations are used when mousing over the
			//rectangles
			itemA.relations = [itemA, subsumer, itemB];
			subsumer.relations = [itemA, subsumer, itemB]
			itemB.relations = [itemA, subsumer, itemB];
			
			//note: this is temporary!!  If we change the input file, this will need to change
			//this forces the human items onto the first axis and the mouse items on the last axis 
			if (itemA.organism == "Human" && itemB.organism == "Mouse") {
				//axis_item_list[0].push(colA_label);
				//axis_item_list[1].push(sub_label);
				//axis_item_list[2].push(colB_label);

			  comparison_data.push(itemA);
			  comparison_data.push(subsumer);
			  comparison_data.push(itemB);
		    }

		});
	}
	});

	
	//Initialize the slider based on the min and max LCS Score from the entire dataset
	var score_max = d3.max(comparison_data, function(d) { return d.score.toFixed(2); });
	var score_min = d3.min(comparison_data, function(d) { return d.score.toFixed(2);});
	lcs_slider.slider({
		range: "max",
		//note: the parseFloat is important in the slider.
		//without it, the slider tends to set the max and min to NaN. 
		min: parseFloat(score_min),
		max: parseFloat(score_max),
		step: (score_max-score_min)/5,
		//values: [ score_min+2, score_max-1 ],
		//value: score_min,
		slide: function( event, ui ) {
			 if (ui.value < ui.min) {
				 ui.value = ui.min;
				 return false;
			 }
			 $( "#curr_score" ).val( ui.value );
			 console.log("in slide min:(" + ui.min + ") max:(" + ui.max + ") value:(" + ui.value + ")");
			 var new_data = comparison_data.filter(function(d){
		    	 return d.score.toFixed(2) >= ui.value.toFixed(2);
			 });
			 filtered_data = new_data.slice();
			 //init();
			 update();
		},
	});
	 console.log("in slide min:(" + $( "#lcs_slider" ).slider( "option", "min") + ") max:(" + $( "#lcs_slider" ).slider( "option","max") + ") value:(" + $( "#lcs_slider" ).slider( "value") + ")");

	$( "#curr_score" ).val( $( "#lcs_slider" ).slider( "value" ) );
	$('#min_score').val(score_min + "");
	$('#max_score').val(score_max + "");

	filtered_data = comparison_data.slice();

	//temp
	generateModelData();

}

//this is a temporary function
function generateModelData() {
	//using the comparison data, create some dummy model data.
	//this model data will be replaced by Monarch API calls later
	//Math.floor((Math.random()*10)+1);
	//filter the dataset to only return the last column
	var temp_data = filtered_data.filter(function(d) {
		return d.axis == 2;
	});
	//for each model, add a binary random number representing 
	//whether or not a phenotype is present in a model
	for (var z=0;z<temp_data.length;z++) {
		for (var y=0;y<model_list.length;y++) {
			var temp_object = {};
			temp_object["label"] = temp_data[z].label;
			temp_object["id"] = temp_data[z].id;
			var model_found = Math.random();
			//var model_found = (Math.floor((Math.random()*10)+1) <= 5) ? 0 : 1;
			temp_object["model"] = [model_list[y]];
			//this is temporary just to force some zero values
			temp_object["value"] = (model_found.toFixed(2) > 0.5) ? model_found : 0;
			model_data.push(temp_object);
		}
	}
}

function createModelRegion() {
	//model_x_axis = undefined;
	xScale = d3.scale.ordinal()
    .domain(model_list)
                    .rangeRoundBands([0,300]);
	model_x_axis = d3.svg.axis().scale(xScale).orient("top");
	var model_region = svg.append("g").attr("transform",
			"translate(530,30)")
			.call(model_x_axis)
			//this be some voodoo...
			//to rotate the text, I need to select it as it was added by the axis
		    .selectAll("text")  
		        .style("text-anchor", "end")
		        .style("font-size", "8pt")
		        .attr("x", 55)
		        .attr("dx", "-.8em")
		        .attr("dy", ".15em")
		        .attr("transform", function(d) {
		            return "rotate(-90)" 
		            });

	//create a scale
	color_values = [1.0, 0.75, 0.5, 0.25, 0.0];
	var legend_rects = svg.selectAll("#legend_rect")
	        .data(color_values);
	    legend_rects.enter()
	        .append("rect")
	          .attr("transform","translate(530,30)")
		      .attr("class", "legend_rect")
		      .attr("x", "320")
		      .attr("y", function(d, i) {
		    	  return 150 + (i* 25);
		      })
		      .attr("width", 20)
		      .attr("height", 20)
		      .attr("fill", function(d) {
			  	  return color_scale(d);
			  });
	var legend_text = svg.selectAll("#legend_text")
        .data(color_values);
    legend_text.enter()
	      .append("text")
	          .attr("transform","translate(530,30)")
		      .attr("class", "legend_rect")
		      .attr("x", "345")
		      .attr("y", function(d, i) {
		    	  return (150 + (i* 25)) + 10;
		      })
		      .text(String);

	
	
	
}

function updateAxes() {

	//$("#info_div").hide();
	axis_item_list[0] = [];
	axis_item_list[1] = [];
	axis_item_list[2] = [];
	//add an axis for each ordinal scale found in the data
	for (var i=0;i<dimensions.length;i++)
	{ 
		//first, add all the labels for the given the axis
	    var curr_axis_list = filtered_data.map(function (node) 
		{ if (node.axis == i) 
		  return node.label;
		//else return null;
		});
	    //filter the list of labels and return the unique labels 
	    axis_item_list[i] = curr_axis_list.filter(function(a,b,c){
	    	//this returns a unique set of strings
	    	 return c.indexOf(a, b + 1) < 0;
	    });
	    //axis_item_list[i].sort(function(a,b) { return a.score-b.score;});
	}

	//reset the axis_list and y_scale_list
	axis_list= [];
	y_scale_list = [];

	build_axes(axis_item_list[0]);
	//build_axes(axis_list0.sort());
	build_axes(axis_item_list[1]);
	build_axes(axis_item_list[2]);
	x.domain(axis_list).rangeBands([ h, 0 ], 1);
}

function init() {
	
	//add an axis for each ordinal scale found in the data
	for (var i=0;i<dimensions.length;i++)
	{ 
	    axis_pos_list.push((i*(text_width + 10)) + col_starting_pos);
	}
	
	
    //create accent boxes
	var rect_accents = svg.selectAll("#rect.accent")
	      .data(dimensions, function(d) { return d;});
    rect_accents.enter()
    	.append("rect")
	      .attr("class", "accent")
	      .attr("x", function(d, i) { return axis_pos_list[i];})
	      .attr("y", "0")
	      .attr("width", text_width + 5)
	      .attr("height", h)
	      .style("opacity", '0.4')
	      .attr("fill", function(d, i) {
	    	  return i != 1 ? d3.rgb("#e5e5e5") : "white";
	      });

    
}


//this is a temporary function.
//some comparisons will not involve different organisms
function getOrganism(uri) {
	var pos = uri.indexOf("HP_");
	if (pos == -1) {
		return "Mouse";
	} else {
		return "Human";
	}
}

//return a useful label to use for visualizing the rectangles
function getCleanLabel(uri, label) {
	if (label && label != "" && label != "null") {
		return label;
	} 
    var temp = getConceptId(uri);
    return temp;
}

//This method extracts the unique id from a given URI
//for example, http://www.berkeleybop.org/obo/HP:0003791 would return HP:0003791
//Why?  Two reasons.  First it's useful to note that d3.js doesn't like to use URI's as ids.
//Second, I like to use unique ids for CSS classes.  This allows me to selectively manipulate related groups of items on the
//screen based their relationship to a common concept (ex: HP000123).  However, I can't use a URI as a class.
function getConceptId(uri) {
	var startpos = uri.lastIndexOf("/");
	var len = uri.length;
	//remove the last > if there is one
	var endpos = uri.indexOf(">") == len-1 ? len-1 : len;
	var retString =  uri + "";
	if (startpos != -1) {
		retString = uri.substring(startpos+1,endpos);
	}
	//replace spaces with underscores.  Classes are separated with spaces so
	//a class called "Model 1" will be two classes: Model and 1.  Convert this to "Model_1" to avoid this problem.
	retString = retString.replace(" ", "_");
	return retString;
}

//take another parameter the axis_index [0,1,2].  Filter the data variable where axis = axis_index...
function build_axes(data) {

	var new_scale = d3.scale.ordinal()
	                         .domain(data)
	                         .rangeBands([ h, 0 ], 1);
	var new_axis = d3.svg.axis()
	                       .orient("left")
	                       .scale(new_scale);
	//new_axis.attr("visible", "false");
	axis_list.push(new_axis);
	y_scale_list.push(new_scale);
}

function createRects() {
	// this takes some 'splaining
	//the raw dataset contains repeats of data within the A,subsumer, and B columns.
	//if d3 sees the same label 4 times (ex: Abnormality of the pharynx) then it will
	//create a rectangle and text for it 4 times.  Therefore, I need to create a unique set of 
	//labels per axis (because the labels can repeat across axes)
	
	//check to see if I need this...the data may already be unique
	var unique_labels = [];
	var unique_data = [];
	for (var x=0;x < axis_list.length;x++) {
		var temp_data = filtered_data.filter(function(d) { return d.axis == x;});
		var temp_unique_labels = [];
		temp_data.forEach(function(data_item) {
			if (temp_unique_labels.indexOf(data_item.label) == -1) {
				temp_unique_labels.push(data_item.label);
				unique_data.push(data_item);
			}
		});
	}

	var rect_text = svg
	   .selectAll(".data_text")
	   .data(unique_data, function(d) { return getConceptId(d.id) + "_" + d.axis; });
	rect_text.enter()
	   		.append("foreignObject")
		    .attr("class", function(d) {
			    return "data_text ack " + getConceptId(d.id);
		    })
			.attr("x", function(d) {
				  return axis_pos_list[d.axis];
			})
			.attr("y", function(d) {
				  return y_scale_list[d.axis](d.label)-5;
		     })
			.on("mouseover", function(d) {
				if (clicked_data == undefined) {
					selectData(d);
				}
			})
			.on("mouseout", function(d) {
				if (clicked_data == undefined) {
					resetLinks(d);
				}
			})
			.on("click", function(d) {
				rect_click(d);
			})
		    .attr("width", text_width)
		    .attr("height", 50)
		     .append("xhtml:body")
		     .html(function(d, i) { 
		    	 var fontsize = 8;
		    	// if (d.axis == 1) {
		    	//	 fontsize = 38 - (i%31);
		    	 //}
		    	 return "<div style=\"font-size: " + fontsize + "pt;\">" + d.label + "</div>";
		     })
	rect_text.transition()
	    .delay(1000)
		.attr("y", function(d) {
			return y_scale_list[d.axis](d.label);
		})
   	rect_text.exit()
		.remove();


}


function createLines() {
	//var line_layer = svg.append("g");
	//remove the nodes lacking connection data
	var connection_data = filtered_data.filter(function (d) { return d.hasOwnProperty('connection'); });
	var connection_lines = svg
	  .selectAll(".data_line")
	  .data(connection_data, function (d) { return getConceptId(d.id) + "_" + d.axis + "_" + getConceptId(d.connection.id) + "_" +  d.connection.axis;});
	 connection_lines.enter()
		  .append("line")
			  //.attr("class", "data_line")
			  //start line at the current item
			  .attr("x1", function(d) {
				  return axis_pos_list[d.axis] + text_width;
			  })
			  .attr("y1", function(d) {
				  return y_scale_list[d.axis](d.label) + 15;
			  })
			  .attr("x2", function(d) {
				  return axis_pos_list[d.connection.axis];
			  })
			  .attr("y2", function(d) {
				  return y_scale_list[d.connection.axis](d.connection.label) + 15;
			  })
			  .attr("class", function(d) {
				  var relations = d.relations;
				  var class_list = "data_line ";
				  for (var x=0;x<relations.length;x++) {
					  class_list = class_list + " " + getConceptId(relations[x].id);
				  }
				  return class_list;
			  })
			  .attr("stroke-width", 2)
			  .attr("opacity", "0.2")
			  .attr("stroke", "lightgrey")
	connection_lines.transition()
	    .delay(500)
		  .attr("y1", function(d) {
			  return y_scale_list[d.axis](d.label) + 15;
		  })
		  .attr("y2", function(d) {
			  return y_scale_list[d.connection.axis](d.connection.label) + 15;
		  })
	connection_lines.exit()
		.remove();

}

function resetLinks() {


	var link_lines = d3.selectAll(".data_line");
	link_lines.style("stroke", "lightgrey");
	link_lines.style("stroke-width", 2);
	link_lines.style('opacity', '0.2');

}

function selectData(curr_data) {
	resetLinks();
	var all_links = svg.selectAll("line." + getConceptId(curr_data.id));
	all_links.style("opacity", "1.0");
	all_links.style("stroke", d3.rgb("#ea763b"));
}

function updateClass(obj, classname) {
	obj.addClass(classname);
}

//NOTE: I need to find a way to either add the model class to the phenotypes when they load OR
//select the rect objects related to the model and append the class to them.
//something like this: $( "p" ).addClass( "myClass yourClass" );
function createModelRects() {
	var model_rects = svg.selectAll(".models")
    	.data(model_data);
	model_rects.enter()
		.append("rect")
		.attr("transform",
			"translate(530,18)")
	    .attr("class", function(d) { 
	    	//append the model id to all related items
	    	if (d.value > 0) {
		    	var bla = svg.selectAll(".data_text." + getConceptId(d.id));	    	
		    	bla.classed(getConceptId(d.model), true);
	    	}
	    	return "models " + " " +  getConceptId(d.model) + " " +  getConceptId(d.id);
	    })
	    .attr("y", function(d, i) {     	
	    	return y_scale_list[2](d.label);
	    })
	    .attr("x", function(d) { return xScale(d.model);})
	    .attr("width", 10)
	    .attr("height", 10)
	    .attr("fill", function(d, i) {
	  	  return color_scale(d.value);
	    });
	model_rects.transition()
    .delay(1000)
	.attr("y", function(d) {
		return y_scale_list[2](d.label);
	})
	model_rects.exit()
	.remove();


}


function update() {
	updateAxes();
	createRects();
	createModelRects() ;
	createLines();
}

init();
loadData();
createModelRegion();
update();



